package com.kaigejava.rulesengine.engine.core.api;

import com.alibaba.dubbo.common.utils.NamedThreadFactory;
import com.kaigejava.rulesengine.engine.core.cache.FactDataRunnable;
import com.kaigejava.rulesengine.engine.core.cache.MessageCache;
import com.kaigejava.rulesengine.engine.core.kie.KieContex;
import com.kaigejava.rulesengine.engine.core.kie.KieResponse;
import com.kaigejava.rulesengine.engine.core.kie.RulesMapper;
import com.kaigejava.rulesengine.engine.core.typedefine.DataEntryPoint;
import com.kaigejava.rulesengine.engine.core.utils.KieUtils;
import com.kaigejava.rulesengine.engine.dto.CreRuleDto;
import com.kaigejava.rulesengine.engine.fact.RecyclingFact;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;


/**
 * @author kaigejava
 */
public  class RulesEngine {
    private static Logger logger = LoggerFactory.getLogger("STDERR");
    /**
     * kie contex
     */
    private static KieContex kieContex;
    /**
     * flag
     */
    private static final AtomicBoolean START = new AtomicBoolean(false);
    public static final Integer DEFAULT_RULE_TYPE = 0;
    private static final int MIN_THREADS_SIZE = 1;
    private final static int QUEUE_CAPACITY = 10;
    private final static String RULE_ENGINE_SINGLE_POOL_NAME = "ruleEngineSiglePool";

    /**
     * start RulesEngine
     * @return
     */
    public static RulesEngineApiResponse start() {
        //only START flag false can start RulesEngine
        if (START.compareAndSet(Boolean.FALSE, Boolean.TRUE)) {
            kieContex = KieContex.getKieContex();
            //fact  data thread
            ThreadPoolExecutor executor = new ThreadPoolExecutor(MIN_THREADS_SIZE, MIN_THREADS_SIZE,
                    0L, TimeUnit.MILLISECONDS,
                    new LinkedBlockingQueue<Runnable>(QUEUE_CAPACITY),new NamedThreadFactory(RULE_ENGINE_SINGLE_POOL_NAME));
            executor.execute(new FactDataRunnable(MessageCache.getDataCache()));

        } else {
            return RulesEngineApiResponse.RULES_ENGINE_ALREADY_STARTED;
        }
        return RulesEngineApiResponse.OK;
    }

    /**
     * stop RulesEngine
     * @return
     */
    public static RulesEngineApiResponse stop() {
        //only START flag true can stop RulesEngine
        if (START.compareAndSet(Boolean.TRUE, Boolean.FALSE)) {

        } else {
            return RulesEngineApiResponse.RULES_ENGINE_ALREADY_STOPED;
        }
        return RulesEngineApiResponse.OK;
    }

    /**
     * load rules when rulesengine start
     * @param
     * @return
     */
    public static RulesEngineApiResponse loadRules(List<CreRuleDto> creRuleList) {
        if (!START.get()) {
            return RulesEngineApiResponse.RULES_ENGINE_NO_START;
        }
        for (CreRuleDto creRule: creRuleList ) {
            String drl = null;
            try {
                drl = KieUtils.templatize(creRule);
                RulesMapper.addRule(creRule.getRuleId(),DEFAULT_RULE_TYPE,drl);
                RulesMapper.addRuleName(creRule.getRuleId(),creRule.getRuleName());
            } catch (Exception e) {
                logger.error("Loading rules , compile rule,rule id:{} error,error message:{}.",creRule.getRuleId(),e.getMessage());
            }
        }
        //init kie runtime
        try {
            kieContex.initKieContex(KieContex.STATELESS_SESSION);
        } catch (Exception e) {
            logger.error("RulesEngine init kie runtime env error,error message:{}.",e.getMessage());
            return RulesEngineApiResponse.RULES_ENGINE_INIT_RUNTIME_ERROR;
        }
        return RulesEngineApiResponse.OK;
    }


    /**
     * change rules to engine
     * @param creRuleList
     * @param rulesChangeType
     * @return
     */
    public static RulesEngineApiResponse changeRules(List<CreRuleDto> creRuleList, RulesChangeType rulesChangeType) {
        if (!START.get()) {
            return RulesEngineApiResponse.RULES_ENGINE_NO_START;
        }
        return dynamicallyLoadRules(creRuleList,rulesChangeType);
    }

    /**
     * execute rule
     * @param recyclingFact     ,fact
     * @param dataEntryPoint    ,interface type
     * @param responseMessage   ,get rules executed result
     * @return
     * @throws Exception
     */
    public static RulesEngineApiResponse fireRules(RecyclingFact recyclingFact, KieResponse responseMessage, DataEntryPoint dataEntryPoint) throws Exception {
        if (!START.get()) {
            return RulesEngineApiResponse.RULES_ENGINE_NO_START;
        }
        if (KieContex.getKieContex().isBusy()) {
            //buffer data
            RulesEngine.buffer(recyclingFact);
            return RulesEngineApiResponse.KIE_RUNTIME_BUSY;
        }
        //rule  check
        if (CollectionUtils.isEmpty(RulesMapper.getRules().keySet())
            || CollectionUtils.isEmpty(RulesMapper.getRules().get(recyclingFact.getRuleType()))) {
        }
        try {
            KieResponse kieResponse = KieUtils.fireRules(recyclingFact);
            responseMessage.setCode(kieResponse.getCode());
            responseMessage.setMessage(kieResponse.getMessage());
            responseMessage.setResult(kieResponse.getResult());
        } catch (Exception e) {
            logger.error("{} execute rule exception.",dataEntryPoint.getMsg() );
        }
        return RulesEngineApiResponse.OK;
    }

    /**
     * dynamically load rules
     * @param creRuleList
     * @param rulesChangeType
     */
    private static RulesEngineApiResponse dynamicallyLoadRules(List<CreRuleDto> creRuleList, RulesChangeType rulesChangeType) {
        if (CollectionUtils.isEmpty(creRuleList)) {
            return RulesEngineApiResponse.OK;
        }
        for (CreRuleDto creRuleDto: creRuleList ) {
            String drl = null;
            if (rulesChangeType == RulesChangeType.ADD || rulesChangeType == RulesChangeType.UPDATE) {
                try {
                    drl = KieUtils.templatize(creRuleDto );
                } catch (Exception e) {
                    logger.error("{}",e);
                    continue;
                }
                if (rulesChangeType == RulesChangeType.ADD) {
                    RulesMapper.addRule(creRuleDto.getRuleId(),DEFAULT_RULE_TYPE, drl);
                }
                if (rulesChangeType == RulesChangeType.UPDATE) {
                    RulesMapper.deleteRule(creRuleDto.getRuleId(),DEFAULT_RULE_TYPE);
                    RulesMapper.addRule(creRuleDto.getRuleId(),DEFAULT_RULE_TYPE, drl);
                }
            }else if (rulesChangeType == RulesChangeType.DELETE) {
                RulesMapper.deleteRule(creRuleDto.getRuleId(),DEFAULT_RULE_TYPE);
            }
        }

        //init kie runtime
        try {
            kieContex.initKieContex(KieContex.STATELESS_SESSION);
        } catch (Exception e) {
            logger.error("Dynamically init kie runtime env error,error message:{}.",e.getMessage());
            return RulesEngineApiResponse.RULES_ENGINE_INIT_RUNTIME_ERROR;
        }
        return RulesEngineApiResponse.OK;
    }

    /**
     * buffer RecyclingFact  when kie runtime busy
     * @param fact 规则引擎忙时，把调用数据缓存到内存，等待线程处理
     * @return
     */
    public static RulesEngineApiResponse buffer(RecyclingFact fact) {
        MessageCache.getDataCache().putBufferData(fact, MessageCacheType.facts.getType());
        return RulesEngineApiResponse.OK;
    }

}
